home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue31 / hash2 / HashRecM.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-11-29  |  23.0 KB  |  683 lines

  1. {*********************************************************}
  2. {* HashRecM                                              *}
  3. {* Copyright (c) Julian M Bucknall 1997                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Record manager using hash table as index              *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit HashRecM;
  14.  
  15. interface
  16.  
  17. uses
  18.   {$IFDEF Windows}
  19.   WinProcs, WinTypes,
  20.   {$ELSE}
  21.   Windows,
  22.   {$ENDIF}
  23.   SysUtils;
  24.  
  25. type
  26.   {$IFDEF Windows}
  27.   TMemSize = word;
  28.   {$ELSE}
  29.   TMemSize = integer;
  30.   {$ENDIF}
  31.  
  32. type
  33.   ThrmHashFunction = function (const S : string) : longint;
  34.     {-Function type for a hash function for the record manager}
  35.   ThrmGetKey = function (const aRecord) : string;
  36.     {-Function type to retrieve the key string from a record}
  37.  
  38. type
  39.   ThrmHashRecordManager = class
  40.     private
  41.       hrmArray         : pointer;
  42.       hrmArraySize     : TMemSize;
  43.       hrmBucket        : PByteArray;
  44.       hrmBucketNo      : longint;
  45.       hrmBucketSize    : TMemSize;
  46.       hrmCount         : longint;
  47.       hrmDataHandle    : THandle;
  48.       hrmFileName      : string;
  49.       hrmGetKeyFunc    : ThrmGetKey;
  50.       hrmHashFunc      : ThrmHashFunction;
  51.       hrmIndexHandle   : THandle;
  52.       hrmMustFlush     : boolean;
  53.       hrmRecLen        : integer;
  54.       hrmRecsPerBucket : integer;
  55.       hrmTableSize     : integer;
  56.     protected
  57.       function hrmAddBucket : longint;
  58.       function hrmFindPrim(const aKey       : string;
  59.                              var aBucketNo  : longint;
  60.                              var aBucketInx : integer) : boolean;
  61.       procedure hrmGrowTable;
  62.       function hrmHash(const aKey : string) : integer;
  63.       function hrmGetKey(const aRecord) : string;
  64.       procedure hrmReadBucket(aBucketNo : integer);
  65.       procedure hrmUpdateBucket(aBucketNo : integer);
  66.       procedure hrmUpdateIndex;
  67.     public
  68.       constructor Create(const aFileName  : string;
  69.                                aRecLen    : integer;
  70.                                aTableSize : integer;
  71.                                aHashFunc  : ThrmHashFunction;
  72.                                aGetKeyFunc: ThrmGetKey);
  73.         {-create a hashed record manager. aFileName is the root name
  74.           of the data (.HTD) and index (.HTI) files. aRecLen is the
  75.           record length. aTableSize is the number of elements in the
  76.           hash table and is only required if a new table is being
  77.           created, otherwise it is read from the index file. aHashFunc
  78.           is the routine to hash a string. aGetKeyFunc is the routine
  79.           that returns the key for a given record.}
  80.       destructor Destroy; override;
  81.         {-destroy the record manager. All memory is released, all
  82.           files are closed}
  83.  
  84.       procedure Delete(const aKey : string);
  85.         {-delete the record defined by aKey; an exception is raised
  86.           if the record is not found}
  87.       function Find(const aKey : string; var aRecord) : boolean;
  88.         {-find the record defined by aKey; return true and the
  89.           associated record if the string is found, otherwise false}
  90.       procedure Insert(const aRecord);
  91.         {-insert a new record; an exception is raised if the key
  92.           generated from the record is already present}
  93.  
  94.       property Count : longint read hrmCount;
  95.         {-current number of records in the file}
  96.       property MustFlush : boolean
  97.          read hrmMustFlush write hrmMustFlush;
  98.         {-true if writes to the files must be flushed immediately}
  99.  
  100.       property RecLength : integer read hrmRecLen;
  101.         {-record length}
  102.       property RecsPerBucket : integer read hrmRecsPerBucket;
  103.         {-number of records per bucket}
  104.       property TableSize : integer read hrmTableSize;
  105.         {-number of elements in the hash table}
  106.   end;
  107.  
  108. implementation
  109.  
  110. type
  111.   THashElementState = (hesEmpty, hesDeleted, hesInUse);
  112.  
  113.   THashElement = packed record
  114.     heBucketNo : longint;
  115.   end;
  116.  
  117.   PHashElementArray = ^THashElementArray;
  118.   THashElementArray =
  119.      array [0..pred(MaxInt div sizeof(THashElement))] of THashElement;
  120.  
  121. {===Helper routines==================================================}
  122. procedure RaiseException(const S : string);
  123. begin
  124.   raise Exception.Create(S);
  125. end;
  126. {--------}
  127. function GetClosestPrime(N : integer) : integer;
  128. {$I Primes.inc}
  129. const
  130.   Forever = true;
  131. var
  132.   L, R, M : integer;
  133.   RootN   : integer;
  134.   IsPrime : boolean;
  135.   DivisorIndex : integer;
  136. begin
  137.   {treat 2 as a special case}
  138.   if (N = 2) then begin
  139.     Result := N;
  140.     Exit;
  141.   end;
  142.   {make the result equal to N, and if it's even, the next odd number}
  143.   if Odd(N) then
  144.     Result := N
  145.   else
  146.     Result := succ(N);
  147.   {if the result is within our prime number table, use binary search
  148.    to find the equal or next highest prime number}
  149.   if (Result <= MaxPrime) then begin
  150.     L := 0;
  151.     R := pred(PrimeCount);
  152.     while (L <= R) do begin
  153.       M := (L + R) div 2;
  154.       if (Result = Primes[M]) then
  155.         Exit
  156.       else if (Result < Primes[M]) then
  157.         R := pred(M)
  158.       else
  159.         L := succ(M);
  160.     end;
  161.     Result := Primes[L];
  162.     Exit;
  163.   end;
  164.   {the result is outside our prime number table range, use the
  165.    standard method for testing primality (do any of the primes up to
  166.    the root of the number divide it exactly?) and continue
  167.    incrementing the result by 2 until it is prime}
  168.   if (Result <= (MaxPrime * MaxPrime)) then begin
  169.     while Forever do begin
  170.       RootN := round(Sqrt(Result));
  171.       DivisorIndex := 1; {ignore the prime number 2}
  172.       IsPrime := true;
  173.       while (DivisorIndex < PrimeCount) and (RootN > Primes[DivisorIndex]) do begin
  174.         if ((Result div Primes[DivisorIndex]) * Primes[DivisorIndex] = Result) then begin
  175.           IsPrime := false;
  176.           Break;
  177.         end;
  178.         inc(DivisorIndex);
  179.       end;
  180.       if IsPrime then
  181.         Exit;
  182.       inc(Result, 2);
  183.     end;
  184.   end;
  185. end;
  186. {====================================================================}
  187.  
  188.  
  189. {===File routines====================================================}
  190. procedure HRMClose(aHandle : THandle);
  191. begin
  192.   if (aHandle <> 0) then
  193.     FileClose(aHandle);
  194. end;
  195. {--------}
  196. procedure HRMDeleteFile(const aFileName : string);
  197. begin
  198.   if not SysUtils.DeleteFile(aFileName) then
  199.     RaiseException(Format('HashRecM.HRMDeleteFile: delete of %s failed',
  200.                           [aFileName]));
  201. end;
  202. {--------}
  203. procedure HRMFlush(aHandle : THandle);
  204. {$IFDEF Windows}
  205. var
  206.   DosError : word;
  207. begin
  208.   asm
  209.     mov ah, $68
  210.     mov bx, aHandle
  211.     call DOS3Call
  212.     jc @@Error
  213.     xor ax, ax
  214.   @@Error:
  215.     mov DosError, ax
  216.   end;
  217.   if (DosError <> 0) then
  218.     RaiseException('HashRecM.HRMFlush: flush failed')
  219. end;
  220. {$ELSE}
  221. begin
  222.   if not FlushFileBuffers(aHandle) then
  223.     RaiseException('HashRecM.HRMFlush: flush failed')
  224. end;
  225. {$ENDIF}
  226. {--------}
  227. function HRMOpen(const aFileName : string; aIsNew : boolean) : THandle;
  228. begin
  229.   if aIsNew then begin
  230.     Result := FileCreate(aFileName);
  231.     if (Result = -1) then
  232.       RaiseException('HashRecM.HRMOpen: could not create file');
  233.     FileClose(Result);
  234.   end;
  235.   Result := FileOpen(aFileName, fmOpenReadWrite or fmShareExclusive);
  236.   if (Result = -1) then
  237.     RaiseException('HashRecM.HRMOpen: could not open file');
  238. end;
  239. {--------}
  240. procedure HRMRead(aHandle : THandle;
  241.                   aOffset : longint;
  242.                   aBufSize: longint;
  243.               var aBuffer);
  244. var
  245.   SeekOffset : longint;
  246.   BytesRead  : longint;
  247. begin
  248.   SeekOffset := FileSeek(aHandle, aOffset, 0);
  249.   if (SeekOffset = -1) then
  250.     RaiseException('HashRecM.HRMRead: seek failed');
  251.   BytesRead := FileRead(aHandle, aBuffer, aBufSize);
  252.   if (BytesRead <> aBufSize) then
  253.     RaiseException('HashRecM.HRMRead: read failed');
  254. end;
  255. {--------}
  256. procedure HRMRenameFile(const aOldFileName, aNewFileName : string);
  257. begin
  258.   if not SysUtils.RenameFile(aOldFileName, aNewFileName) then
  259.     RaiseException(Format('HashRecM.HRMRenameFile: rename of %s to %s failed',
  260.                           [aOldFileName, aNewFileName]));
  261. end;
  262. {--------}
  263. function HRMSeek(aHandle : THandle;
  264.                  aOffset : longint;
  265.                  aOrigin : longint) : longint;
  266. begin
  267.   Result := FileSeek(aHandle, aOffset, aOrigin);
  268.   if (Result = -1) then
  269.     RaiseException('HashRecM.HRMSeek: seek failed');
  270. end;
  271. {--------}
  272. procedure HRMWrite(aHandle : THandle;
  273.                    aOffset : longint;
  274.                    aBufSize: longint;
  275.              const aBuffer);
  276. var
  277.   SeekOffset : longint;
  278.   BytesWrit  : longint;
  279. begin
  280.   SeekOffset := FileSeek(aHandle, aOffset, 0);
  281.   if (SeekOffset = -1) then
  282.     RaiseException('HashRecM.HRMWrite: seek failed');
  283.   BytesWrit := FileWrite(aHandle, aBuffer, aBufSize);
  284.   if (BytesWrit <> aBufSize) then
  285.     RaiseException('HashRecM.HRMWrite: write failed');
  286. end;
  287. {====================================================================}
  288.  
  289.  
  290. {===ThrmHashRecordManager===============================================}
  291. constructor ThrmHashRecordManager.Create(const aFileName : string;
  292.                                                aRecLen    : integer;
  293.                                                aTableSize : integer;
  294.                                                aHashFunc  : ThrmHashFunction;
  295.                                                aGetKeyFunc: ThrmGetKey);
  296. var
  297.   FName         : string;
  298.   IndexFileSize : longint;
  299.   IsNew         : boolean;
  300. begin
  301.   inherited Create;
  302.  
  303.   {open up the data and index files}
  304.   hrmFileName := aFileName;
  305.   FName := SysUtils.ChangeFileExt(aFileName, '.HTD');
  306.   IsNew := not FileExists(FName);
  307.   hrmDataHandle := HRMOpen(FName, IsNew);
  308.   FName := SysUtils.ChangeFileExt(aFileName, '.HTI');
  309.   hrmIndexHandle := HRMOpen(FName, IsNew);
  310.  
  311.   {using the index handle, work out the hash table size}
  312.   IndexFileSize := HRMSeek(hrmIndexHandle, 0, 2);
  313.   if (IndexFileSize = 0) then
  314.     hrmTableSize := GetClosestPrime(aTableSize)
  315.   else
  316.     hrmTableSize := (IndexFileSize - sizeof(hrmCount)) div sizeof(THashElement);
  317.  
  318.   {create the hash table}
  319.   hrmArraySize := hrmTableSize * sizeof(THashElement);
  320.   GetMem(hrmArray, hrmArraySize);
  321.  
  322.   {read the old hash table or save the new empty hash table}
  323.   if (IndexFileSize = 0) then begin
  324.     FillChar(hrmArray^, hrmArraySize, $FF);
  325.     hrmUpdateIndex;
  326.   end
  327.   else begin
  328.     HRMRead(hrmIndexHandle, 0, sizeof(hrmCount), hrmCount);
  329.     HRMRead(hrmIndexHandle, sizeof(hrmCount), hrmArraySize, hrmArray^);
  330.   end;
  331.  
  332.   {calculate the bucket size, create a bucket buffer}
  333.   hrmBucketSize := 4*1024;
  334.   GetMem(hrmBucket, hrmBucketSize);
  335.  
  336.   {remember the functions, the record length, the record count per
  337.    bucket}
  338.   hrmHashFunc := aHashFunc;
  339.   hrmGetKeyFunc := aGetKeyFunc;
  340.   hrmRecLen := aRecLen;
  341.   hrmRecsPerBucket := hrmBucketSize div succ(aRecLen);
  342. end;
  343. {--------}
  344. destructor ThrmHashRecordManager.Destroy;
  345. begin
  346.   {force the index to be updated}
  347.   if (hrmIndexHandle <> 0) then
  348.     hrmUpdateIndex;
  349.   {close the files}
  350.   HRMClose(hrmDataHandle);
  351.   HRMClose(hrmIndexHandle);
  352.   {free memory}
  353.   if (hrmArray <> nil) then
  354.     FreeMem(hrmArray, hrmArraySize);
  355.   if (hrmBucket <> nil) then
  356.     FreeMem(hrmBucket, hrmBucketSize);
  357.   {destroy ancestor}
  358.   inherited Destroy;
  359. end;
  360. {--------}
  361. procedure ThrmHashRecordManager.Delete(const aKey : string);
  362. var
  363.   BNo  : longint;
  364.   BInx : integer;
  365. begin
  366.   if not hrmFindPrim(aKey, BNo, BInx) then
  367.     RaiseException('ThrmHashRecordManager.Delete: key not found');
  368.   hrmBucket^[BInx * succ(hrmRecLen)] := ord(hesDeleted);
  369.   hrmUpdateBucket(BNo);
  370.   dec(hrmCount);
  371. end;
  372. {--------}
  373. function ThrmHashRecordManager.Find(const aKey : string; var aRecord) : boolean;
  374. var
  375.   BNo  : longint;
  376.   BInx : integer;
  377. begin
  378.   if hrmFindPrim(aKey, BNo, BInx) then begin
  379.     Result := true;
  380.     Move(hrmBucket^[succ(BInx * succ(hrmRecLen))], aRecord, hrmRecLen);
  381.   end
  382.   else begin
  383.     Result := false;
  384.   end;
  385. end;
  386. {--------}
  387. function ThrmHashRecordManager.hrmAddBucket : longint;
  388. var
  389.   EOF : longint;
  390. begin
  391.   FillChar(hrmBucket^, hrmBucketSize, 0);
  392.   EOF := HRMSeek(hrmDataHandle, 0, 2);
  393.   Result := EOF div hrmBucketSize;
  394.   HRMWrite(hrmDataHandle, EOF, hrmBucketSize, hrmBucket^);
  395.   if MustFlush then
  396.     HRMFlush(hrmDataHandle);
  397.   hrmBucketNo := Result;
  398. end;
  399. {--------}
  400. function ThrmHashRecordManager.hrmFindPrim(const aKey       : string;
  401.                                              var aBucketNo  : longint;
  402.                                              var aBucketInx : integer) : boolean;
  403. var
  404.   FirstDelBucket    : longint;
  405.   FirstDelBucketInx : integer;
  406.   KeyHash           : integer;
  407.   FirstKeyHash      : integer;
  408.   RecOffset         : longint;
  409.   RecNo             : integer;
  410. begin
  411.   {Note: this routine either returns True to say the key was found or
  412.          False if it wasn't. If True, aBucketNo is the bucket number
  413.          in the data file and aBucketInx is the number of the record
  414.          in the bucket (and the global hrmBucket field has the bucket
  415.          in it). If False then aBucketNo/aBucketInx is the address of
  416.          the first deleted record (and hrmBucket has the bucket in it)
  417.          or aBucketNo is -1 to say that a new bucket is required. If
  418.          aBucketInx is -1 then the hash table is completely full;
  419.          otherwise aBucketInx is the element number in the hash table
  420.          where the new bucket number is to be stored.}
  421.  
  422.   {assume we'll fail}
  423.   Result := false;
  424.   {we may need to make note of the first deleted bucket we find, so
  425.    set the variable to some impossible value so that we know whether
  426.    we found one yet}
  427.   FirstDelBucket := -1;
  428.   FirstDelBucketInx := -1;
  429.   {calculate the hash for the string, make a note of it so we can find
  430.    out when (if) we wrap around the table completely}
  431.   KeyHash := hrmHash(aKey);
  432.   FirstKeyHash := KeyHash;
  433.   {do forever - we'll be exiting out of the loop when needed}
  434.   while true do begin
  435.     {with the current element...}
  436.     with PHashElementArray(hrmArray)^[KeyHash] do begin
  437.       {if the bucket number is -1 then the element is empty; we must
  438.        stop the linear probe and return either this index or the first
  439.        deleted one we encountered}
  440.       if (heBucketNo = -1) then begin
  441.         if (FirstDelBucket <> -1) then begin
  442.           aBucketNo := FirstDelBucket;
  443.           aBucketInx := FirstDelBucketInx;
  444.         end
  445.         else begin
  446.           aBucketNo := -1;
  447.           aBucketInx := KeyHash;
  448.         end;
  449.         Exit;
  450.       end;
  451.       {otherwise the element is used; retrieve the bucket from disk}
  452.       hrmReadBucket(heBucketNo);
  453.       {for each record in this bucket, check its state}
  454.       for RecNo := 0 to pred(hrmRecsPerBucket) do begin
  455.         RecOffset := RecNo * succ(hrmRecLen);
  456.         case THashElementState(hrmBucket^[RecOffset]) of
  457.           hesEmpty   : begin
  458.                          {the state is 'empty', we must stop the
  459.                           linear probe and return either this bucket
  460.                           and index or the first deleted one we found}
  461.                          if (FirstDelBucket <> -1) then begin
  462.                            aBucketNo := FirstDelBucket;
  463.                            aBucketInx := FirstDelBucketInx;
  464.                          end
  465.                          else begin
  466.                            aBucketNo := heBucketNo;
  467.                            aBucketInx := RecNo;
  468.                          end;
  469.                          Exit;
  470.                        end;
  471.           hesDeleted : begin
  472.                          {the state is 'deleted', we must make a note
  473.                           of this index if it's the first one we found
  474.                           and continue the linear probe}
  475.                          if (FirstDelBucket = -1) then begin
  476.                            FirstDelBucket := heBucketNo;
  477.                            FirstDelBucketInx := RecNo;
  478.                          end;
  479.                        end;
  480.           hesInUse   : begin
  481.                          {the state is 'in use', we check to see if
  482.                           it's our string, if it is, exit returning
  483.                           true and the index}
  484.                          if (hrmGetKey(hrmBucket^[succ(RecOffset)]) = aKey) then begin
  485.                            aBucketNo := heBucketNo;
  486.                            aBucketInx := RecNo;
  487.                            Result := true;
  488.                            Exit;
  489.                          end;
  490.                        end;
  491.         else
  492.           {bad news}
  493.           RaiseException('ThrmHashRecordManager.hrmFindPrim: invalid element state')
  494.         end;{case}
  495.       end;
  496.     end;
  497.     {we didn't find the key or an empty slot this time around, so
  498.      increment the index (taking care of the wraparound) and exit if
  499.      we've got back to the start again}
  500.     inc(KeyHash);
  501.     if (KeyHash = hrmTableSize) then
  502.       KeyHash := 0;
  503.     if (KeyHash = FirstKeyHash) then begin
  504.       if (FirstDelBucket <> -1) then begin
  505.         aBucketNo := FirstDelBucket;
  506.         aBucketInx := FirstDelBucketInx;
  507.       end
  508.       else begin
  509.         aBucketNo := -1;
  510.         aBucketInx := -1;
  511.       end;
  512.       Exit;
  513.     end;
  514.   end;{forever loop}
  515. end;
  516. {--------}
  517. procedure ThrmHashRecordManager.hrmGrowTable;
  518. var
  519.   UndoLevel    : integer;                             
  520.   Bucket, Inx  : integer;
  521.   NewTableSize : integer;
  522.   OldTableSize : integer;
  523.   OldArraySize : TMemSize;
  524.   NewArraySize : TMemSize;
  525.   OldCount     : longint;
  526.   RecOffset    : integer;
  527.   BucketCount  : longint;
  528.   OldDataHandle: THandle;
  529.   OldBucket    : PByteArray;
  530.   NewArray     : PHashElementArray;
  531.   OldArray     : PHashElementArray;
  532.   FName, TempFName : string;
  533. begin
  534.   {allocate a bucket for our use}
  535.   GetMem(OldBucket, hrmBucketSize);
  536.   try
  537.     {save the old array, element count, etc}
  538.     OldArray := PHashElementArray(hrmArray);
  539.     OldArraySize := hrmArraySize;
  540.     OldTableSize := hrmTableSize;
  541.     OldCount := hrmCount;
  542.     try
  543.       {track the amount of work we do, in case something goes wrong
  544.        and we have to undo - we shall aim to leave the record manager
  545.        in the same state is was when we started if an error occurs}
  546.       UndoLevel := 0;
  547.       {allocate a new array roughly twice as large as before}
  548.       NewTableSize := GetClosestPrime(succ(hrmTableSize * 2));
  549.       NewArraySize := NewTableSize * sizeof(THashElement);
  550.       GetMem(NewArray, NewArraySize);
  551.       FillChar(NewArray^, NewArraySize, $FF);
  552.       {set the new data}
  553.       hrmArray := NewArray;
  554.       hrmArraySize := NewArraySize;
  555.       hrmTableSize := NewTableSize;
  556.       hrmCount := 0;
  557.       UndoLevel := 1;
  558.       {calculate the number of buckets in the current data file}
  559.       BucketCount := HRMSeek(hrmDataHandle, 0, 2) div hrmBucketSize;
  560.       {close the current data file, rename it, open it again}
  561.       FName := SysUtils.ChangeFileExt(hrmFileName, '.HTD');
  562.       TempFName := SysUtils.ChangeFileExt(hrmFileName, '.SAV');
  563.       HRMClose(hrmDataHandle);
  564.       UndoLevel := 2;
  565.       HRMRenameFile(FName, TempFName);
  566.       UndoLevel := 3;
  567.       OldDataHandle := HRMOpen(TempFName, false);
  568.       UndoLevel := 4;
  569.       {create a new data file}
  570.       hrmDataHandle := HRMOpen(FName, true);
  571.       UndoLevel := 5;
  572.       {read through the old file and transfer over the records}
  573.       for Bucket := 0 to pred(BucketCount) do begin
  574.         HRMRead(OldDataHandle, (Bucket * hrmBucketSize), hrmBucketSize, OldBucket^);
  575.         for Inx := 0 to pred(hrmRecsPerBucket) do begin
  576.           RecOffset := Inx * succ(hrmRecLen);
  577.           if (THashElementState(OldBucket^[RecOffset]) = hesInUse) then begin
  578.             Insert(OldBucket^[succ(RecOffset)]);
  579.           end;
  580.         end;
  581.       end;
  582.       {close the old handle and delete the file}
  583.       try
  584.         HRMClose(OldDataHandle);
  585.         HRMDeleteFile(TempFName);
  586.       except
  587.       end;
  588.       {free the old hash array}
  589.       FreeMem(OldArray, OldTableSize * sizeof(THashElement));
  590.       {note that we don't need to ensure the index file is updated,
  591.        since the Inserts above will have done so}
  592.     except
  593.       if (UndoLevel >= 5) then begin
  594.         HRMClose(hrmDataHandle);
  595.         HRMDeleteFile(FName);
  596.       end;
  597.       if (UndoLevel >= 4) then
  598.         HRMClose(OldDataHandle);
  599.       if (UndoLevel >= 3) then
  600.         HRMRenameFile(TempFName, FName);
  601.       if (UndoLevel >= 2) then
  602.         hrmDataHandle := HRMOpen(FName, false);
  603.       if (UndoLevel >= 1) then
  604.         FreeMem(NewArray, NewTableSize * sizeof(THashElement));
  605.       hrmArray := OldArray;
  606.       hrmArraySize := OldArraySize;
  607.       hrmTableSize := OldTableSize;
  608.       hrmCount := OldCount;
  609.       raise;
  610.     end;
  611.   finally
  612.     FreeMem(OldBucket, hrmBucketSize);
  613.   end;
  614. end;
  615. {--------}
  616. function ThrmHashRecordManager.hrmHash(const aKey : string) : integer;
  617. begin
  618.   if not Assigned(hrmHashFunc) then
  619.     RaiseException('ThrmHashRecordManager.hrmHash: no hash function defined');
  620.   Result := hrmHashFunc(aKey) mod hrmTableSize
  621. end;
  622. {--------}
  623. function ThrmHashRecordManager.hrmGetKey(const aRecord) : string;
  624. begin
  625.   if not Assigned(hrmGetKeyFunc) then
  626.     RaiseException('ThrmHashRecordManager.hrmGetKey: no get key function defined');
  627.   Result := hrmGetKeyFunc(aRecord);
  628. end;
  629. {--------}
  630. procedure ThrmHashRecordManager.hrmReadBucket(aBucketNo : integer);
  631. begin
  632.   HRMRead(hrmDataHandle, (aBucketNo * hrmBucketSize), hrmBucketSize, hrmBucket^);
  633.   hrmBucketNo := aBucketNo;
  634. end;
  635. {--------}
  636. procedure ThrmHashRecordManager.hrmUpdateBucket(aBucketNo : integer);
  637. begin
  638.   HRMWrite(hrmDataHandle,
  639.            (aBucketNo * hrmBucketSize),
  640.            hrmBucketSize,
  641.            hrmBucket^);
  642.   if MustFlush then
  643.     HRMFlush(hrmDataHandle);
  644. end;
  645. {--------}
  646. procedure ThrmHashRecordManager.hrmUpdateIndex;
  647. begin
  648.   HRMWrite(hrmIndexHandle, 0, sizeof(hrmCount), hrmCount);
  649.   HRMWrite(hrmIndexHandle, sizeof(hrmCount), hrmArraySize, hrmArray^);
  650.   if MustFlush then
  651.     HRMFlush(hrmIndexHandle);
  652. end;
  653. {--------}
  654. procedure ThrmHashRecordManager.Insert(const aRecord);
  655. var
  656.   BNo  : longint;
  657.   BInx : integer;
  658.   RecOffset : longint;
  659. begin
  660.   if hrmFindPrim(hrmGetKey(aRecord), BNo, BInx) then
  661.     RaiseException('ThrmHashRecordManager.Insert: duplicate key');
  662.   if (BNo = -1) then begin
  663.     if (BInx = -1) then
  664.       RaiseException('ThrmHashRecordManager.Insert: table is full');
  665.     BNo := hrmAddBucket;
  666.     PHashElementArray(hrmArray)^[BInx].heBucketNo := BNo;
  667.     hrmUpdateIndex;
  668.     BInx := 0;
  669.   end;
  670.   if (BNo <> hrmBucketNo) then
  671.     hrmReadBucket(BNo);
  672.   RecOffset := BInx * succ(hrmRecLen);
  673.   hrmBucket^[RecOffset] := ord(hesInUse);
  674.   Move(aRecord, hrmBucket^[succ(RecOffset)], hrmRecLen);
  675.   hrmUpdateBucket(BNo);
  676.   inc(hrmCount);
  677.   if ((hrmCount * 3) > (longint(hrmTableSize) * 2 * hrmRecsPerBucket)) then
  678.     hrmGrowTable;
  679. end;
  680. {====================================================================}
  681.  
  682. end.
  683.